package com.lqian.p1_concurrent_origin_3;

/**
 * 并发编程中还有没有诡异的Bug呢？
 * 有的，就是 有序性
 * 顾名思义：有序性指的是程序按照代码的先后顺序执行，但是编译器为了优化性能，有时候会改变程序语句的先后顺序，
 * 例如程序中： a = 5; b = 6; 编译器优化以后的结果可能会是 b = 6; a = 5;
 * 在这个程序中，编译器调整了语句的顺序，但是不影响程序的最终结果。
 * 不过，有时候编译器及解释器的优化可能导致意想不到的Bug
 *
 * 在Java领域一个经典的案例就是利用双重检查创建单例对象：
 *      在获取实例 getInstance() 的方法中，我们首先判断 instance 是否为空，
 *      如果为空，则锁定 Singleton.class 并再次检查 instance 是否为空，如果还为空则创建 Singleton 的一个实例。
 */
public class SequenceDemo {

}


class Singleton{

    //对instance进行volatile语义声明，就可以禁止指令重排序，避免该情况发生。
    // final
    static Singleton instance;
    static Singleton getInstance(){
        if (instance ==null){
            synchronized (Singleton.class){
                if (instance == null);{
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

    /**
     *假设有两个线程 A、B 同时调用 getInstance() 方法，他们会同时发现 instance == null，
     * 于是同时对 Singleton.class 加锁，此时 JVM 保证只有一个线程能够加锁成功（假设是线程 A），
     * 另外一个线程则会处于等待状态（假设是线程 B）；线程 A 会创建一个 Singleton 实例，之后释放锁，
     * 锁释放后，线程 B 被唤醒，线程 B 再次尝试加锁，此时是可以加锁成功的，加锁成功后，
     * 线程 B 检查 instance == null 时会发现，已经创建过 Singleton 实例了，
     * 所以线程 B 不会再创建一个 Singleton 实例。
     *
     * 这看上去一切都很完美，无懈可击，但实际上这个 getInstance() 方法并不完美。
     * 问题出在哪里呢？
     * 出在 new 操作上，理论上我们以为的 new 操作应该是：
     *  1: 分配一块内存M
     *  2: 在内存M上初始化Singleton对象
     *  3: 然后M的地址赋值给instance变量
     *  --->
     *      但实际上优化后的执行路径却是这样的：
     *   1：分配一块内存M
     *   2：将M的地址赋值给instance变量
     *   3：最后在内存M上初始化该Singleton对象
     *   ---> 优化后会导致什么问题呢？
     *    我们假设线程A先执行getInstance方法,当执行完指令2时恰好发生了线程切换，切换到了线程B上；
     *    如果此时线程B也执行getInstance方法，那么线程B在执行第一个判断时会发现instance != null，所以直接返回instance
     *    而此时的instance是没有初始化过的，如果我们这个时候访问instance的成员变量就可能触发空指针异常
     *
     *    JSR-133中的happens-before规则。
     *    1.一个线程中的每个操作先于线程中的后续操作。
     *    2.对一个锁的解锁先于随后对这个锁的加锁。
     *    3.传递行。
     *
     */

}